import dao.EquipmentMap;
import model.CRCModel;
import model.EquipmentModel;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.*;

public class IoContext {
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        exit();
    }

    private static String myPath;
    private final static String ip = ".";
    private final static int port = 60000;
    private final static int ReadMaxPoolSize = 3;
    private final static int WriteMaxPoolSize = 3;
    private final static int ReadCorePoolSize = 2;
    private final static int ToDBMaxPoolSize = 10;
    private final static int ToDBCorePoolSize = 3;
    private final static int WriteCorePoolSize = 2;
    private static ExecutorService toDataBasePool;

    /**
     * 设备地址最大值
     */
    private final static int AddressCount = 255;
    /**
     * 读取缓冲大小
     */
    public final static int ReadBufferSize = 125;
    /**
     * 传感器数量最大值
     */
    public final static int SensorCount = 6;
    /**
     * 读取每个设备的间隔时间
     */
    public final static long EquipmentWriteTime = 1000 * 30;//10分钟
    /**
     * 扫描每个设备的间隔时间
     */
    public static long EquipmentWriteEvery;
    /**
     * 读取每个通道的间隔时间
     */
    public final static long EquipmentChannelTime = 1000;

    /**
     * 批次时间
     */
    public final static long BatchTimeOut = 1000 * 8;
    /**
     * 能量最小值
     */
    public final static float PowerMinValue = -42;
    /**
     * 能量最大值
     */
    public final static float PowerMaxValue = -20;
    /**
     * 波长最大值
     */
    public final static float WaveMaxValue = 1563.0f;
    /**
     * 波长最小值
     */
    public final static float WaveMinValue = 1525.0f;
    /**
     * myBatis数据连接
     */
    private static SqlSession sqlSession;
    private static Map<Integer, byte[][]> commandMap = new HashMap<>(AddressCount);
    private static List<EquipmentModel> equipmentModels = new ArrayList<>();

    public static SqlSession getSqlSession() {
        return sqlSession;
    }

    public static ExecutorService getToDataBasePool() {
        return toDataBasePool;
    }

    public static String getIp() {
        return ip;
    }

    public static int getPort() {
        return port;
    }

    public static void exit() {
        commandMap.clear();
        equipmentModels.clear();
        if (sqlSession != null) {
            sqlSession.close();
        }
        close();
    }

    public static String getMainDataBatchKey() {
        return getMainKeyLen("AA", (short) 0);
    }

    /**
     * 得到长型主键
     * 规则：标识(2个字符) + 年月日时分秒 + 时间戳 + 端口号
     *
     * @param signName 主键标识
     * @param port     端口号
     * @return 例如：CC2019082710251215668727122052344
     */
    private static String getMainKeyLen(String signName, short port) {
        return getIdMainKeyPrivate(signName, (byte) 2, port, true);
    }

    private static String getIdMainKeyPrivate(String signName, byte signLen, short port, boolean lenType) {
        if (signName == null) {
            return null;
        }
        if (signName != null && signName.length() != signLen) {
            return null;
        }
        int max = 1000, min = 1;//1000以内
        int i = (int) (Math.random() * (max - min) + min);
        if (i < 10) {
            signName = String.format("%s00%d", signName, i);
        } else if (i > 9 && i < 100) {
            signName = String.format("%s0%d", signName, i);
        } else {
            signName = String.format("%s%d", signName, i);
        }
        String myDate;
        String myTime = Long.valueOf(System.currentTimeMillis()).toString();
        SimpleDateFormat df;//设置日期格式
        if (lenType) {
            df = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        } else {
            df = new SimpleDateFormat("yyyyMMddSSS");
            myTime = myTime.substring(myTime.length() - 1 - 4, myTime.length() - 1);
        }
        myDate = df.format(new Date());
        myDate = myDate.substring(2);
        if (port > 1024)
            return String.format("%s%s%s%d", signName, myDate, myTime, port);
        else
            return String.format("%s%s%s", signName, myDate, myTime);
    }

    private static void enableToDataBasePool() {
        toDataBasePool = new ThreadPoolExecutor(ToDBCorePoolSize
                , ToDBMaxPoolSize
                , 1000
                , TimeUnit.MILLISECONDS
                , new ArrayBlockingQueue<Runnable>(10)
                , Executors.defaultThreadFactory()
                , new ThreadPoolExecutor.AbortPolicy());
    }

    /**
     * 开启读写线程池
     */
    private static void enableReadWritePool() {
        //maximumPoolSize设置为2 ，拒绝策略为AbortPolic策略，直接抛出异常
        //使用ArrayBlockingQueue有界任务队列，若有新的任务需要执行时，线程池会创建新的线程
        // ，直到创建的线程数量达到corePoolSize时，则会将新的任务加入到等待队列中。
        // 若等待队列已满，即超过ArrayBlockingQueue初始化的容量，则继续创建线程，
        // 直到线程数量达到maximumPoolSize设置的最大线程数量，
        // 若大于maximumPoolSize，则执行拒绝策略。
        // 在这种情况下，线程数量的上限与有界任务队列的状态有直接关系，
        // 如果有界队列初始容量较大或者没有达到超负荷的状态，线程数将一直维持在corePoolSize以下，
        // 反之当任务队列已满时，则会以maximumPoolSize为最大线程数上限。
//        readPool = new ThreadPoolExecutor(ReadCorePoolSize
//                , ReadMaxPoolSize
//                , 1000
//                , TimeUnit.MILLISECONDS
//                , new ArrayBlockingQueue<Runnable>(10)
//                , Executors.defaultThreadFactory()
//                , new ThreadPoolExecutor.AbortPolicy());
//        writePool = new ThreadPoolExecutor(WriteCorePoolSize
//                , WriteMaxPoolSize
//                , 1000
//                , TimeUnit.MILLISECONDS
//                , new ArrayBlockingQueue<Runnable>(10)
//                , Executors.defaultThreadFactory()
//                , new ThreadPoolExecutor.AbortPolicy());
//        for (int i = 0; i < 3; i++) {
//            readPool.execute(new ThreadTask());
//            writePool.execute(new ThreadTask());
//        }
    }

    private static char getAddress(int address) {
        char result = 0x00;
        for (int i = 0; i < address; i++) {
            result += 0x01;
        }
        return result;
    }

    private static char getChannel(int channel) {
        char result = 0x00;
        int index = 0;
        while (index < channel) {
            result += 0x14;
            index++;
        }
        return result;
    }

//    /**
//     * 向右边加空格
//     *
//     * @param message 消息
//     * @param size    结果长度
//     * @return
//     */
//    public static String getTrimString(String message, int size) {
//        if (message == null) {
//            return null;
//        }
//        final int length = message.length();
//        if (length < size) {
//            for (int i = 0; i < size - length; i++) {
//                message += " ";
//            }
//            return message;
//        } else if (length > size)
//            return message.substring(0, size);
//        else
//            return message;
//    }

    private static char[] getBaseCommand(char command) {
        int index = 0;
        char[] baseCommand = new char[4];
        baseCommand[index++] = 0x7F;
        baseCommand[index++] = command;
        final CRCModel crcSite = getCRCSite(baseCommand, baseCommand.length - 2);
        baseCommand[index++] = crcSite.getCRC16Lo();
        baseCommand[index++] = crcSite.getCRC16Hi();
        return baseCommand;
    }

    /**
     * @return 注册命令
     */
    public static char[] getRegisterCommand() {
        return getBaseCommand((char) 0x10);
    }

    /**
     * @return 退出命令
     */
    public static char[] getExitCommand() {
        return getBaseCommand((char) 0x11);
    }

    private static boolean myCRE(char[] data) {
        if (data == null || data.length == 0) {
            return false;
        }
        final CRCModel crcSite = getCRCSite(data, data.length - 2);
        if (data[data.length - 1] == crcSite.getCRC16Hi() && data[data.length - 2] == crcSite.getCRC16Lo()) {
            return true;
        } else {
            return false;
        }
    }

    public static boolean myCRCByte(byte[] data) {
        Charset cs = Charset.forName("ISO-8859-1");
        ByteBuffer bb = ByteBuffer.allocate(data.length);
        bb.put(data);
        bb.flip();
        CharBuffer cb = cs.decode(bb);
        return myCRE(cb.array());
    }

    private static char[] getSendCommand(int address, int channel) {
        int index = 0;
        char[] command = new char[8];
        command[index++] = getAddress(address);
        command[index++] = 0x03;
        command[index++] = 0x00;
        command[index++] = getChannel(channel);
        command[index++] = 0x00;
        command[index++] = 0x14;

        final CRCModel crcSite = getCRCSite(command, command.length - 2);
        command[index++] = crcSite.getCRC16Lo();
        command[index++] = crcSite.getCRC16Hi();
        return command;
    }

    private static CRCModel getCRCSite(char[] bytes) {
        return getCRCSite(bytes, bytes.length - 2);
    }

    /**
     * 得到CRC校验码
     *
     * @param bytes
     * @param length
     * @return
     */
    private static CRCModel getCRCSite(char[] bytes, int length) {
        char SaveHi, SaveLo;
        int Flag;
        char CRC16Lo = 0xFF;
        char CRC16Hi = 0xFF;
        char CL = 0x01;
        char CH = 0xA0;

        CRCModel crcModel = new CRCModel();
        for (int i = 0; i < length; i++) {
            CRC16Lo = (char) (CRC16Lo ^ bytes[i]); //'每一个数据与CRC寄存器进行异或
            for (Flag = 0; Flag < 8; Flag++) {
                SaveHi = CRC16Hi;
                SaveLo = CRC16Lo;
                CRC16Hi = (char) (CRC16Hi >> 1); //'高位右移一位
                CRC16Lo = (char) (CRC16Lo >> 1); //'低位右移一位
                if ((SaveHi & 0x01) == 0x01) {  //'如果高位字节最后一位为1
                    CRC16Lo = (char) (CRC16Lo | 0x80);  //'则低位字节右移后前面补1
                }                                  //'否则自动补0
                if ((SaveLo & 0x01) == 0x01) {    //'如果LSB为1，则与多项式码进行异或
                    CRC16Hi = (char) (CRC16Hi ^ CH);
                    CRC16Lo = (char) (CRC16Lo ^ CL);
                }
            }
        }
        crcModel.setCRC16Lo(CRC16Lo);
        crcModel.setCRC16Hi(CRC16Hi);
        return crcModel;
    }

    private static byte[] getCommandByte(char[] data) {
//        Charset cs = Charset.forName("ISO-8859-1");
        byte[] bytes = new byte[data.length];
        for (int i = 0; i < data.length; i++) {
            bytes[i] = (byte) data[i];
        }
        return bytes;
    }

    /**
     * 根据地址与传感器编号得到命令
     *
     * @param address 设备地址
     * @param channel 传感器编号
     * @return
     */
    public static byte[] getCommand(int address, int channel) {
        if (address < 0 || address > AddressCount || channel < 0 || channel > SensorCount) {
            return null;
        }
        final byte[][] bytes = commandMap.get(address);
        if (bytes != null && bytes.length > 0)
            return bytes[channel];
        else
            return null;
    }

    private static void createCommand() {
        for (int i = 0; i < AddressCount; i++) {
            byte[][] commandByte = new byte[SensorCount][8];
            for (int j = 0; j < SensorCount; j++) {
                commandByte[j] = getCommandByte(getSendCommand(i, j));
            }
            commandMap.put(i, commandByte);
        }
    }

    private static void setUpParam() {
        EquipmentWriteEvery = (EquipmentWriteTime - EquipmentChannelTime * SensorCount) / 2;
        if (EquipmentWriteEvery < 1000)
            EquipmentWriteEvery = 1000;
    }

    public static void createContext(String path) {
        try {
            myPath = getJarPath(path);
            if (iniMybatis(myPath)) {
                getEquipment();
            }
            createCommand();
            setUpParam();
            enableToDataBasePool();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static EquipmentModel getEquipmentByIp(String ip, int port) {
        boolean finder = false;
        EquipmentModel equipmentModel = new EquipmentModel();
        if (equipmentModels != null) {
            for (EquipmentModel row :
                    equipmentModels) {
                if (row.getIp().equals(ip) && row.getPort() == port) {
                    finder = true;
                    equipmentModel = row;
                }
            }
        }
        return finder ? equipmentModel : null;
    }

    private static void getEquipment() {
        if (sqlSession == null) {
            return;
        }
        equipmentModels.clear();

        EquipmentMap equipmentMap = sqlSession.getMapper(EquipmentMap.class);
        equipmentModels = equipmentMap.getAllEquipment(null);
    }

    private static String getJarPath(String path) {
        String jarPath = null;
        final String[] split = path.split("/");
        for (int i = 0; i < split.length - 1; i++) {
            if (split[i] != null && split[i].length() > 0) {
                if (jarPath != null) {
                    jarPath = jarPath + split[i] + File.separator;
                } else {
                    jarPath = split[i] + File.separator;
                }
            }
        }
        return jarPath;
    }

    public static int unsignedByteToInt(byte b) {
        return (int) b & 0xFF;
    }

    public static String byteToHex(byte b) {
        int i = b & 0xFF;
        return Integer.toHexString(i);
    }

    public static short getShort(byte[] b, int index) {
        return (short) (((b[index + 1] << 8) | b[index + 0] & 0xff));
    }

    public static float byte2float(byte[] b, int index) {
        int l;
        l = b[index + 0];
        l &= 0xff;
        l |= ((long) b[index + 1] << 8);
        l &= 0xffff;
        l |= ((long) b[index + 2] << 16);
        l &= 0xffffff;
        l |= ((long) b[index + 3] << 24);
        return Float.intBitsToFloat(l);
    }

    public static void writeToTxt(byte[] bytes) throws IOException {
        OutputStream outputStream = new FileOutputStream("D:/b.txt", false);
        InputStream inputStream = new ByteArrayInputStream(bytes);
        byte[] buff = new byte[bytes.length];
        int len;
        while ((len = inputStream.read(buff)) != -1) {
            outputStream.write(buff, 0, len);
        }
        inputStream.close();
        outputStream.close();
    }

    private static boolean iniMybatis(String path) throws IOException {
        String resource = "";
        if (path.indexOf(":") < 0)
            resource = File.separator;
        resource = resource + path + "SqlMapConfig.xml";
        System.out.println(resource);
        File file = new File(resource);
        if (!file.exists()) {
            System.out.println(String.format("%s文件不存在", resource));
            return false;
        }
        InputStream inputStream = new FileInputStream(resource);

//        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = factory.openSession();
        return true;
    }

    public static void close() {
        sqlSession.close();
        commandMap.clear();
        equipmentModels.clear();
        if (!toDataBasePool.isShutdown())
            toDataBasePool.shutdown();
    }


}
